1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.collect;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.collect.CollectPreconditions.checkNonnegative;
21
22 import com.google.caliper.BeforeExperiment;
23 import com.google.caliper.Benchmark;
24 import com.google.caliper.Param;
25 import com.google.common.annotations.VisibleForTesting;
26 import com.google.common.primitives.Ints;
27 import com.google.common.util.concurrent.ThreadFactoryBuilder;
28
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Random;
33 import java.util.Set;
34 import java.util.concurrent.Callable;
35 import java.util.concurrent.ConcurrentHashMap;
36 import java.util.concurrent.ConcurrentMap;
37 import java.util.concurrent.ExecutionException;
38 import java.util.concurrent.ExecutorService;
39 import java.util.concurrent.Executors;
40 import java.util.concurrent.Future;
41
42 import javax.annotation.Nullable;
43
44
45
46
47
48
49 public class ConcurrentHashMultisetBenchmark {
50 @Param({"1", "2", "4", "8"}) int threads;
51 @Param({"3", "30", "300"}) int size;
52 @Param MultisetSupplier implSupplier;
53
54 private Multiset<Integer> multiset;
55 private ImmutableList<Integer> keys;
56 private ExecutorService threadPool;
57
58 @BeforeExperiment void setUp() throws Exception {
59 multiset = implSupplier.get();
60 ImmutableList.Builder<Integer> builder = ImmutableList.builder();
61 for (int i = 0; i < size; i++) {
62 builder.add(i);
63 }
64 keys = builder.build();
65 threadPool =
66 Executors.newFixedThreadPool(threads, new ThreadFactoryBuilder().setDaemon(true).build());
67 }
68
69 @Benchmark long add(final int reps) throws ExecutionException, InterruptedException {
70 return doMultithreadedLoop(
71 new Callable<Long>() {
72 @Override public Long call() {
73 return runAddSingleThread(reps);
74 }
75 });
76 }
77
78 @Benchmark long addRemove(final int reps) throws ExecutionException, InterruptedException {
79 return doMultithreadedLoop(
80 new Callable<Long>() {
81 @Override public Long call() {
82 return runAddRemoveSingleThread(reps);
83 }
84 });
85 }
86
87 private long doMultithreadedLoop(Callable<Long> task)
88 throws InterruptedException, ExecutionException {
89
90 List<Future<Long>> futures = Lists.newArrayListWithCapacity(threads);
91 for (int i = 0; i < threads; i++) {
92 futures.add(threadPool.submit(task));
93 }
94 long total = 0;
95 for (Future<Long> future : futures) {
96 total += future.get();
97 }
98 return total;
99 }
100
101 private long runAddSingleThread(int reps) {
102 Random random = new Random();
103 int nKeys = keys.size();
104 long blah = 0;
105 for (int i = 0; i < reps; i++) {
106 Integer key = keys.get(random.nextInt(nKeys));
107 int delta = random.nextInt(5);
108 blah += delta;
109 multiset.add(key, delta);
110 }
111 return blah;
112 }
113
114 private long runAddRemoveSingleThread(int reps) {
115 Random random = new Random();
116 int nKeys = keys.size();
117 long blah = 0;
118 for (int i = 0; i < reps; i++) {
119 Integer key = keys.get(random.nextInt(nKeys));
120
121
122 int delta = random.nextInt(10) - 5;
123 blah += delta;
124 if (delta >= 0) {
125 multiset.add(key, delta);
126 } else {
127 multiset.remove(key, -delta);
128 }
129 }
130 return blah;
131 }
132
133 private enum MultisetSupplier {
134 CONCURRENT_HASH_MULTISET() {
135 @Override Multiset<Integer> get() {
136 return ConcurrentHashMultiset.create();
137 }
138 },
139 BOXED_ATOMIC_REPLACE() {
140 @Override Multiset<Integer> get() {
141 return OldConcurrentHashMultiset.create();
142 }
143 },
144 SYNCHRONIZED_MULTISET() {
145 @Override Multiset<Integer> get() {
146 return Synchronized.multiset(HashMultiset.<Integer>create(), null);
147 }
148 },
149 ;
150
151 abstract Multiset<Integer> get();
152 }
153
154
155
156
157
158 private static final class OldConcurrentHashMultiset<E> extends AbstractMultiset<E> {
159
160 private final transient ConcurrentMap<E, Integer> countMap;
161
162
163
164
165
166 public static <E> OldConcurrentHashMultiset<E> create() {
167 return new OldConcurrentHashMultiset<E>(new ConcurrentHashMap<E, Integer>());
168 }
169
170 @VisibleForTesting OldConcurrentHashMultiset(ConcurrentMap<E, Integer> countMap) {
171 checkArgument(countMap.isEmpty());
172 this.countMap = countMap;
173 }
174
175
176
177
178
179
180
181
182
183 @Override public int count(@Nullable Object element) {
184 try {
185 return unbox(countMap.get(element));
186 } catch (NullPointerException e) {
187 return 0;
188 } catch (ClassCastException e) {
189 return 0;
190 }
191 }
192
193
194
195
196
197
198
199
200 @Override public int size() {
201 long sum = 0L;
202 for (Integer value : countMap.values()) {
203 sum += value;
204 }
205 return Ints.saturatedCast(sum);
206 }
207
208
209
210
211
212
213 @Override public Object[] toArray() {
214 return snapshot().toArray();
215 }
216
217 @Override public <T> T[] toArray(T[] array) {
218 return snapshot().toArray(array);
219 }
220
221
222
223
224
225 private List<E> snapshot() {
226 List<E> list = Lists.newArrayListWithExpectedSize(size());
227 for (Multiset.Entry<E> entry : entrySet()) {
228 E element = entry.getElement();
229 for (int i = entry.getCount(); i > 0; i--) {
230 list.add(element);
231 }
232 }
233 return list;
234 }
235
236
237
238
239
240
241
242
243
244
245
246
247
248 @Override public int add(E element, int occurrences) {
249 if (occurrences == 0) {
250 return count(element);
251 }
252 checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences);
253
254 while (true) {
255 int current = count(element);
256 if (current == 0) {
257 if (countMap.putIfAbsent(element, occurrences) == null) {
258 return 0;
259 }
260 } else {
261 checkArgument(occurrences <= Integer.MAX_VALUE - current,
262 "Overflow adding %s occurrences to a count of %s",
263 occurrences, current);
264 int next = current + occurrences;
265 if (countMap.replace(element, current, next)) {
266 return current;
267 }
268 }
269
270 }
271 }
272
273
274
275
276
277
278
279
280
281
282
283 @Override public int remove(@Nullable Object element, int occurrences) {
284 if (occurrences == 0) {
285 return count(element);
286 }
287 checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences);
288
289 while (true) {
290 int current = count(element);
291 if (current == 0) {
292 return 0;
293 }
294 if (occurrences >= current) {
295 if (countMap.remove(element, current)) {
296 return current;
297 }
298 } else {
299
300 @SuppressWarnings("unchecked")
301 E casted = (E) element;
302
303 if (countMap.replace(casted, current, current - occurrences)) {
304 return current;
305 }
306 }
307
308 }
309 }
310
311
312
313
314
315
316
317
318
319 private int removeAllOccurrences(@Nullable Object element) {
320 try {
321 return unbox(countMap.remove(element));
322 } catch (NullPointerException e) {
323 return 0;
324 } catch (ClassCastException e) {
325 return 0;
326 }
327 }
328
329
330
331
332
333
334
335
336
337
338
339
340
341 public boolean removeExactly(@Nullable Object element, int occurrences) {
342 if (occurrences == 0) {
343 return true;
344 }
345 checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences);
346
347 while (true) {
348 int current = count(element);
349 if (occurrences > current) {
350 return false;
351 }
352 if (occurrences == current) {
353 if (countMap.remove(element, occurrences)) {
354 return true;
355 }
356 } else {
357 @SuppressWarnings("unchecked")
358 E casted = (E) element;
359 if (countMap.replace(casted, current, current - occurrences)) {
360 return true;
361 }
362 }
363
364 }
365 }
366
367
368
369
370
371
372
373
374 @Override public int setCount(E element, int count) {
375 checkNonnegative(count, "count");
376 return (count == 0)
377 ? removeAllOccurrences(element)
378 : unbox(countMap.put(element, count));
379 }
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394 @Override public boolean setCount(E element, int oldCount, int newCount) {
395 checkNonnegative(oldCount, "oldCount");
396 checkNonnegative(newCount, "newCount");
397 if (newCount == 0) {
398 if (oldCount == 0) {
399
400 return !countMap.containsKey(element);
401 } else {
402 return countMap.remove(element, oldCount);
403 }
404 }
405 if (oldCount == 0) {
406 return countMap.putIfAbsent(element, newCount) == null;
407 }
408 return countMap.replace(element, oldCount, newCount);
409 }
410
411
412
413 @Override Set<E> createElementSet() {
414 final Set<E> delegate = countMap.keySet();
415 return new ForwardingSet<E>() {
416 @Override protected Set<E> delegate() {
417 return delegate;
418 }
419 @Override public boolean remove(Object object) {
420 try {
421 return delegate.remove(object);
422 } catch (NullPointerException e) {
423 return false;
424 } catch (ClassCastException e) {
425 return false;
426 }
427 }
428 };
429 }
430
431 private transient EntrySet entrySet;
432
433 @Override public Set<Multiset.Entry<E>> entrySet() {
434 EntrySet result = entrySet;
435 if (result == null) {
436 entrySet = result = new EntrySet();
437 }
438 return result;
439 }
440
441 @Override int distinctElements() {
442 return countMap.size();
443 }
444
445 @Override public boolean isEmpty() {
446 return countMap.isEmpty();
447 }
448
449 @Override Iterator<Entry<E>> entryIterator() {
450 final Iterator<Map.Entry<E, Integer>> backingIterator =
451 countMap.entrySet().iterator();
452 return new Iterator<Entry<E>>() {
453 @Override public boolean hasNext() {
454 return backingIterator.hasNext();
455 }
456
457 @Override public Multiset.Entry<E> next() {
458 Map.Entry<E, Integer> backingEntry = backingIterator.next();
459 return Multisets.immutableEntry(backingEntry.getKey(),
460 backingEntry.getValue());
461 }
462
463 @Override public void remove() {
464 backingIterator.remove();
465 }
466 };
467 }
468
469 @Override public void clear() {
470 countMap.clear();
471 }
472
473 private class EntrySet extends AbstractMultiset<E>.EntrySet {
474 @Override Multiset<E> multiset() {
475 return OldConcurrentHashMultiset.this;
476 }
477
478
479
480
481
482
483 @Override public Object[] toArray() {
484 return snapshot().toArray();
485 }
486
487 @Override public <T> T[] toArray(T[] array) {
488 return snapshot().toArray(array);
489 }
490
491 private List<Multiset.Entry<E>> snapshot() {
492 List<Multiset.Entry<E>> list = Lists.newArrayListWithExpectedSize(size());
493
494 Iterators.addAll(list, iterator());
495 return list;
496 }
497
498 @Override public boolean remove(Object object) {
499 if (object instanceof Multiset.Entry) {
500 Multiset.Entry<?> entry = (Multiset.Entry<?>) object;
501 Object element = entry.getElement();
502 int entryCount = entry.getCount();
503 return countMap.remove(element, entryCount);
504 }
505 return false;
506 }
507
508
509
510
511 @Override public int hashCode() {
512 return countMap.hashCode();
513 }
514 }
515
516
517
518
519 private static int unbox(@Nullable Integer i) {
520 return (i == null) ? 0 : i;
521 }
522 }
523 }